#pragma once
#include "Validator.h" 
#include "Ground.h"
#include "GameController.h"
#include "WorldInfoController.h"
#include "Human.h"
#include "Tree.h"
#include "HeightMapCoordinates.h"
#include "FerrisWheel.h"
#include "BalloonsShop.h"
#include "HeightMapCoordinates.h"
#include "Model3DUtils.h"

#include "SharedConstants.h"
#include "GameConstants.h"

namespace
  {
  const TVector3 directions[] = {TVector3( 1, 0, 0) * GroundConstants::CELL_SIZE,
    TVector3(-1, 0, 0) * GroundConstants::CELL_SIZE,
    TVector3(0, -1, 0) * GroundConstants::CELL_SIZE,
    TVector3(0,  1, 0) * GroundConstants::CELL_SIZE};

  bool GetEntity(EWorldEntityTypes& o_cell_type, const TVector3 &i_point, const WorldInfoController &i_control)
    {
    TCell cell = ConvertTVectorToTCell(TVector2(i_point.X(), i_point.Y()));

    if(i_control.GetEntityOnCell(o_cell_type, cell))
      return true;
    return false;
    }

  /**
  *  FerrisWheel takes 3x3 cells
  *  Can't place FerrisWheel near map edge
  */
  bool CheckForFerrisWheel(TCell i_cell, std::shared_ptr<WorldInfoController> i_control, const Ground* ip_ground)
    {
    if (i_cell.first == 0 || i_cell.first == 1 || i_cell.second == 0 || i_cell.second == 1 || 
      i_cell.first == GroundConstants::CELL_COUNT - 2 || i_cell.first == GroundConstants::CELL_COUNT - 1 ||
      i_cell.second == GroundConstants::CELL_COUNT - 2 || i_cell.second == GroundConstants::CELL_COUNT - 1)
      return false;

    for (size_t i = i_cell.first - 1; i <= i_cell.first + 1; ++i)
      for (size_t j = i_cell.second - 1; j <= i_cell.second + 1; ++j)
        if (i_control->IsCellFree(TCell(i, j)) && Model3DUtils::CanPlaceObjectOn(TCell(i, j), ip_ground))
          {
          EWorldEntityTypes cell_type = EWorldEntityTypes::WE_GRASS;
          bool cell_check = i_control->GetEntityOnCell(cell_type, TCell(i, j));
          if (cell_check == true)
            if (cell_type != EWorldEntityTypes::WE_GRASS)
              return false;
          }
        else
          return false;
    return true;
    }
  
  bool CheckForWalkway(const TTriangle2& i_triangle, const WorldInfoController& i_control)
    {
    // checking all 3 points of the triangle
    for(size_t i = 0; i < 2; ++i)
      {
      TCell cell = ConvertTVectorToTCell(i_triangle.V[i]);
      EWorldEntityTypes cell_type;
      if(i_control.GetEntityOnCell(cell_type, cell) && cell_type == WE_WALKWAY)
        {
        if(HeightMapUtils::CellBelowTriangle(cell.first, cell.second, i_triangle))
          return true;
        }
       }
    return false;
    }
  }

bool Validator::CanObjectBePlacedHere(const TVector3& i_point, const World& i_world, const GameController &i_control, EWorldEntityTypes i_type) const
  {
  if(i_type != WE_GRASS && i_type != WE_WALKWAY)
    {
    bool is_walkway_here = _IsPointAtWalkway(i_point, *i_world.GetGround());
    if(is_walkway_here)
      return false;
    }
  else
    {
    if(i_type == WE_GRASS && _IsPointAtGrass(i_point, *i_world.GetGround()))
      {
      return false;
      }
    if(i_type == WE_WALKWAY && _IsPointAtWalkway(i_point, *i_world.GetGround()))
      {
      return false;
      }
    }

  TCell cell = HeightMapUtils::FindHeightMapIndexes(TVector2(i_point.X(), i_point.Y()));
  bool is_cell_free = i_world.GetWorldInfoController()->IsCellFree(cell);
  if(!is_cell_free)
    return false;
  
  // check size of cell
  if (!Model3DUtils::CanPlaceObjectOn(cell, i_world.GetGround()))
    {
    return false;
    }

  if (i_type == WE_FERRIS)
    {
    if (CheckForFerrisWheel(cell, i_world.GetWorldInfoController(), i_world.GetGround()) == false)
      return false;
    }

  int cost = -1;
  switch(i_type)
    {
    case WE_GRASS:
      cost = GameConstants::GRASS_COST;
      break;
    case WE_WALKWAY:
      cost = GameConstants::WALKWAY_COST;
      break;
    case WE_WATER:
      cost = GameConstants::WATER_COST;
      break;
    case WE_TREE:
      cost = GameConstants::TREE_COST;
      break;
    case WE_FERRIS:
      cost = GameConstants::FERRIS_WHEEL_COST;
      break;
    case WE_BALLOONS_SHOP:
      cost = GameConstants::BALLOONS_SHOP_COST;
      break;
    case WE_BENCH:
      cost = GameConstants::BENCH_COST;
      break;
    default:
      assert("Bad type of object!");
      break;
    }  
  if(i_control.GetMoney() < cost)
    return false;

  if(i_type == WE_GRASS) // Prohibit grass placement near BShop and bench
    {
    return !(_IsPointNearObject(i_point, *i_world.GetWorldInfoController(), WE_BENCH)) &&
           !(_IsPointNearObject(i_point, *i_world.GetWorldInfoController(), WE_BALLOONS_SHOP));
    }

  if (WE_BENCH == i_type || WE_BALLOONS_SHOP == i_type)
      {
    return _IsPointNearWalkway(i_point, *i_world.GetGround());
    }
  return true;
  }

bool Validator::_IsPointAtWalkway(const TVector3& i_point, const Ground& i_ground) const
  {
  if (_IsPointInGroundRange(i_point) && i_ground.GetGroundType(TVector2(i_point[0], i_point[1])) == GT_WALKWAY)
    return true;
  else 
    return false;
  };

bool Validator::_IsPointAtGrass(const TVector3& i_point, const Ground& i_ground) const
  {
  if (_IsPointInGroundRange(i_point) && i_ground.GetGroundType(TVector2(i_point[0], i_point[1])) == GT_GRASS)
    return true;
  else 
    return false;
  }

bool Validator::_IsPointInGroundRange(const TVector3& i_point) const
  {
  return i_point.X() >= 0 && i_point.X() < GroundConstants::CELL_COUNT * GroundConstants::CELL_SIZE &&
         i_point.Y() >= 0 && i_point.Y() < GroundConstants::CELL_COUNT * GroundConstants::CELL_SIZE;
  }

bool Validator::CanChangeTerrain(const Model3D& i_changing_triangles, const WorldInfoController &i_wi_control) const
  {
  for(auto it = i_changing_triangles.Triangles().begin(); it != i_changing_triangles.Triangles().end(); it++)
    {
    //check for grass around
    TVector3 sum_point = (it->GetPoint(0) + it->GetPoint(1) + it->GetPoint(2));
    TVector3 mid_point = TVector3(sum_point.X() / 3.0, sum_point.Y() / 3.0, sum_point.Z() / 3.0);
    EWorldEntityTypes entity_type;
    if (GetEntity(entity_type, mid_point, i_wi_control) && entity_type != EWorldEntityTypes::WE_GRASS)
      {
      return false;
      }
    }
  return true;
  }

bool Validator::_IsPointNearWalkway(const TVector3& i_point, const Ground& i_ground) const
  {
  for (TVector3 dr : directions)
    {
    if (_IsPointAtWalkway(i_point + dr, i_ground))
      return true;
    }

      return false;
    }

bool Validator::_IsPointNearObject(const TVector3& i_point, const WorldInfoController& i_wi_control, EWorldEntityTypes i_object) const
  {
  for (TVector3 dr : directions)
    {
    EWorldEntityTypes entity_type;
    if (GetEntity(entity_type, i_point + dr, i_wi_control) && entity_type == i_object)
      return true;
    }

  return false;
  }

bool Validator::CanPlaceWater(const Model3D& i_water_part, const WorldInfoController &i_wi_control) const
  {
  for(auto it = i_water_part.Triangles().begin(); it != i_water_part.Triangles().end(); it++)
    {
    TTriangle3 model_triangle(it->GetPoint(0), it->GetPoint(1), it->GetPoint(2));
    if(CheckForWalkway(GetProjectionOnXY(model_triangle), i_wi_control))
      return false;
    }
  return true;
  }